/* eslint-disable @typescript-eslint/no-use-before-define */
import { RefObject } from 'react';
import {
  DeviceEventEmitter,
  EmitterSubscription,
  ScrollView
} from 'react-native';
import AliyunOSS from 'aliyun-oss-react-native';
import moment from 'moment';
import Sound from 'react-native-sound';
import RNFS from 'react-native-fs';

import { AppApi, ClassApi } from '../../../../api';
import { Message } from '../../../../api/class/getMessageList';
import Log from '../../../../common/log';
import { isIOS } from '../../../../constant';
import { getUserInfo } from '../../../../store/user';
import {
  CLASS_NEW_MESSAGE,
  CLASS_READ_MESSAGE,
  CLASS_WITH_DRAW_MESSAGE
} from '../../../../util/pushMsg';
import {
  NewMessageData,
  ReadMessageData,
  WithDrawMessageData
} from '../../../../util/pushMsg/types';
import { MessageType } from './constant';
import { getLastMessageIdWithLessonId } from './store/messageId';
import { ChatRoomProps, Dispatch, State } from './types';
import { CLASS_ROOM_EVENT_CURRENT_TIME_CHANGE } from '../../event';
import { CurrentTimeChangeData } from '../../types';
import { playSoundWithUrl } from '../../../../util/sound';

let dispatch: Dispatch;
let state: State;
let recordScroll: RefObject<ScrollView>;
let soundPlayer: Sound | null = null;

const TAG = 'ChatRoom';

let newMessageSubscription: EmitterSubscription;
let readMessageSubscription: EmitterSubscription;
let withDrawMessageSubscription: EmitterSubscription;
let currentTimeChangeSubscription: EmitterSubscription;

export const stopSoundPlayer = (): void => {
  if (soundPlayer) {
    soundPlayer.release();
    soundPlayer = null;
  }
};

export default (d: Dispatch, s: State): void => {
  dispatch = d;
  state = s;
};

export const setRecordScroll = (scroll: RefObject<ScrollView>): void => {
  recordScroll = scroll;
};

export const recordScrollToEnd = (): void => {
  setTimeout((): void => {
    if (recordScroll && recordScroll.current) {
      recordScroll.current.scrollToEnd();
    }
  }, 200);
};

export const setMessageList = (value: Message[]): void => {
  dispatch({ type: 'SET_MESSAGE_LIST', value });
};

export const setVodMessageList = (value: Message[]): void => {
  dispatch({ type: 'SET_VOD_MESSAGE_LIST', value });
};

export const setBubbleMessageList = (value: Message[]): void => {
  Log.i(TAG, JSON.stringify({ event: 'setBubbleMessageList', value }));
  dispatch({ type: 'SET_BUBBLE_MESSAGE_LIST', value });
};

export const setUnReadNumber = (value: number): void => {
  Log.i(TAG, JSON.stringify({ event: 'setUnReadNumber', value }));
  dispatch({ type: 'SET_UN_READ_NUMBER', value });
};

export const initChatRoomProps = (value: ChatRoomProps): void => {
  dispatch({ type: 'INIT_CHAT_ROOM_PROPS', value });
};

export const setShowRecord = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowRecord', value }));
  dispatch({ type: 'SET_SHOW_RECORD', value });
  readTextMessages();
};

export const getMessageList = async (
  friendId: number,
  lessonId: number
): Promise<void> => {
  const res = await ClassApi.getMessageList({ friendId, lessonId });
  const { code, data } = res;
  if (code !== 0) return;

  const { messages } = data;
  setMessageList(messages);
};

export const getReplayMessageList = async (
  friendId: number,
  lessonId: number
): Promise<void> => {
  const res = await ClassApi.getReplayMessageList({ friendId, lessonId });
  const { code, data } = res;
  if (code !== 0) return;

  const { messages } = data;
  setVodMessageList(messages);
};

export const readMessage = async (messageId: number): Promise<void> => {
  const { lessonId } = state;
  const res = await ClassApi.readMessage({ messageId, lessonId });
  const { code } = res;
  if (code !== 0) return;
  let { messageList } = state;
  messageList = messageList.map(
    (message): Message => ({
      ...message,
      isRead: message.messageId === messageId ? true : message.isRead
    })
  );
  setMessageList(messageList);
};

export const addSendingMessage = (audioUrl: string, duration: number): void => {
  const { assistantInfo } = state;
  const { id } = assistantInfo;
  const { userId } = getUserInfo();
  const newMessage: Message = {
    messageId: -1,
    senderId: Number(userId),
    receiverId: Number(id),
    type: MessageType.Voice,
    textMessage: {
      text: ''
    },
    audioMessage: {
      audioUrl,
      duration: Math.round(duration)
    },
    lessonEventMessage: {
      eventType: -10
    },
    withdraw: false,
    isRead: false,
    sendTime: moment().format('YYYY-MM-DD HH:mm:ss'),
    isSending: true
  };
  let { messageList } = state;
  messageList = [...messageList, newMessage];
  setMessageList(messageList);
  recordScrollToEnd();
};

export const sendMessage = async (
  duration: number,
  audioUrl: string
): Promise<void> => {
  const { lessonId, assistantInfo } = state;
  const { id } = assistantInfo;
  const res = await ClassApi.sendMessage({
    receiverId: Number(id),
    type: MessageType.Voice,
    lessonId,
    audioMessage: {
      audioUrl,
      duration: Math.round(duration)
    }
  });
  const { code, data } = res;
  if (code !== 0) {
    let { messageList: localMessageList } = state;
    localMessageList = localMessageList.filter(
      (message): boolean => message.messageId !== -1
    );
    setMessageList(localMessageList);
    return;
  }

  const { messageId, sendTime } = data;
  let { messageList: localMessageList } = state;
  localMessageList = localMessageList.map(
    (message): Message => ({
      ...message,
      messageId: message.messageId === -1 ? messageId : message.messageId,
      sendTime: message.messageId === -1 ? sendTime : message.sendTime,
      isSending: false,
      audioMessage: {
        ...message.audioMessage,
        audioUrl:
          message.messageId === -1 ? audioUrl : message.audioMessage.audioUrl
      }
    })
  );
  setMessageList(localMessageList);
};

export const getAudioPreUploadInfo = async (
  audioPath: string,
  duration: number
): Promise<void> => {
  addSendingMessage(audioPath, duration);
  const res = await AppApi.getAudioPreUploadInfo({
    audioType: 2,
    audioName: `student-${new Date().getTime()}.mp3`
  });
  const { code, data } = res;
  if (code !== 0) {
    let { messageList: localMessageList } = state;
    localMessageList = localMessageList.filter(
      (message): boolean => message.messageId !== -1
    );
    setMessageList(localMessageList);
    RNFS.unlink(audioPath);
    return;
  }

  const { ossPath, ossSTS, audioUrl } = data;
  const { accessKeyId, accessKeySecret, stsToken, endPoint, bucket } = ossSTS;
  AliyunOSS.initWithSecurityToken(
    stsToken,
    accessKeyId,
    accessKeySecret,
    endPoint
  );
  AliyunOSS.asyncUpload(
    bucket,
    ossPath,
    isIOS ? audioPath : `file://${audioPath}`
  )
    .then((): void => {
      sendMessage(duration, audioUrl);
      RNFS.unlink(audioPath);
    })
    .catch((): void => {
      RNFS.unlink(audioPath);
    });
};

export const withDrawMessageWithMessageId = (messageId: number): void => {
  let { messageList, bubbleMessageList } = state;
  messageList = messageList.map(
    (message): Message => ({
      ...message,
      withdraw: message.messageId === messageId ? true : message.withdraw
    })
  );
  bubbleMessageList = bubbleMessageList.map(
    (message): Message => ({
      ...message,
      withdraw: message.messageId === messageId ? true : message.withdraw
    })
  );
  setMessageList(messageList);
  setBubbleMessageList(bubbleMessageList);
};

export const withDrawMessage = async (messageId: number): Promise<void> => {
  const { lessonId, messageList } = state;
  const isCurrentMessage = !!messageList.find(
    (message): boolean => message.messageId === messageId
  );
  if (isCurrentMessage) {
    stopSoundPlayer();
  }
  const res = await ClassApi.withDrawMessage({ messageId, lessonId });
  const { code } = res;
  if (code !== 0) return;
  withDrawMessageWithMessageId(messageId);
};

export const getMessageRemindNum = async (
  lessonId: number,
  friendId: number
): Promise<void> => {
  const { userId } = getUserInfo();
  const messageId = getLastMessageIdWithLessonId(lessonId, Number(userId));
  const res = await ClassApi.getMessageRemindNum({
    lastSeeMessageId: messageId,
    lessonId,
    friendId
  });
  const { code, data } = res;
  if (code !== 0) return;

  const { remindNum } = data;
  setUnReadNumber(remindNum);
};

export const readTextMessages = async (): Promise<void> => {
  const { lessonId, assistantInfo } = state;
  const { id } = assistantInfo;
  const res = await ClassApi.readTextMessages({
    lessonId,
    friendId: Number(id)
  });
  const { code } = res;
  if (code !== 0) return;
  let { messageList, bubbleMessageList } = state;
  messageList = messageList.map(
    (message): Message => ({
      ...message,
      isRead: message.type === MessageType.Text ? true : message.isRead
    })
  );
  bubbleMessageList = bubbleMessageList.map(
    (message): Message => ({
      ...message,
      isRead: message.type === MessageType.Text ? true : message.isRead
    })
  );
  setMessageList(messageList);
  setBubbleMessageList(bubbleMessageList);
};

export const deleteBubbleMessageWithId = (messageId: number): void => {
  let { bubbleMessageList } = state;
  bubbleMessageList = bubbleMessageList.filter(
    (message): boolean => message.messageId !== messageId
  );
  setBubbleMessageList(bubbleMessageList);
};

export const playMessageWithId = (message: Message): void => {
  const { messageId, isRead, senderId } = message;
  const { userId } = getUserInfo();
  if (!isRead && Number(userId) !== Number(senderId)) {
    readMessage(messageId);
  }
  let { messageList } = state;
  messageList = messageList.map(
    (m): Message => {
      const isPlay = m.messageId === messageId;
      if (isPlay) {
        stopSoundPlayer();
        soundPlayer = playSoundWithUrl({
          url: message.audioMessage.audioUrl,
          playEnd: (): void => {
            playNextMessage();
          }
        });
      }
      return {
        ...m,
        isPlay
      };
    }
  );
  setMessageList(messageList);
};

export const playNextMessage = (): void => {
  let { messageList } = state;
  const { userId } = getUserInfo();
  const currentPlayMessageIndex = messageList.findIndex(
    (message): boolean => !!message.isPlay
  );
  messageList = messageList.map(
    (message): Message => ({
      ...message,
      isPlay: false
    })
  );
  if (currentPlayMessageIndex !== -1) {
    const lastMessageList = messageList.filter(
      (message, index): boolean => index > currentPlayMessageIndex
    );
    const nextMessage = lastMessageList.find(
      (message): boolean =>
        message.type === MessageType.Voice &&
        !message.isRead &&
        Number(message.senderId) !== Number(userId)
    );
    if (nextMessage) {
      if (!nextMessage.isRead) {
        readMessage(nextMessage.messageId);
      }
      messageList = messageList.map(
        (message): Message => {
          const isPlay = message.messageId === nextMessage.messageId;
          if (isPlay) {
            stopSoundPlayer();
            soundPlayer = playSoundWithUrl({
              url: message.audioMessage.audioUrl,
              playEnd: (): void => {
                playNextMessage();
              }
            });
          }
          return {
            ...message,
            isPlay
          };
        }
      );
    }
  }
  setMessageList(messageList);
};

export const stopPlayMessage = (): void => {
  stopSoundPlayer();
  let { messageList } = state;
  messageList = messageList.map(
    (message): Message => ({
      ...message,
      isPlay: false
    })
  );
  setMessageList(messageList);
};

export const startReceiveNewMessageMsg = (): void => {
  newMessageSubscription = DeviceEventEmitter.addListener(
    CLASS_NEW_MESSAGE,
    (data: NewMessageData): void => {
      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveNewMessageMsg', ...data })
      );
      const { userId } = getUserInfo();
      const {
        messageId,
        senderId,
        type,
        textMessage,
        audioMessage,
        lessonEventMessage,
        sendTime
      } = data;

      const newMessage: Message = {
        messageId,
        senderId,
        receiverId: Number(userId),
        type,
        textMessage,
        audioMessage,
        lessonEventMessage,
        withdraw: false,
        isRead: false,
        sendTime
      };
      let { messageList, bubbleMessageList } = state;
      const { unReadNumber } = state;
      const { showRecord } = state;
      messageList = [...messageList, newMessage];
      bubbleMessageList = [...bubbleMessageList, newMessage];
      setMessageList(messageList);
      if (!showRecord) {
        setBubbleMessageList(bubbleMessageList);
        setTimeout((): void => {
          setUnReadNumber(unReadNumber + 1);
          deleteBubbleMessageWithId(messageId);
        }, 3 * 1000);
      }
      recordScrollToEnd();

      if (type === MessageType.Text) {
        readTextMessages();
      }
    }
  );
};

export const stopReceiveNewMessageMsg = (): void => {
  if (newMessageSubscription) {
    newMessageSubscription.remove();
  }
};

export const startReceiveReadMessageMsg = (): void => {
  readMessageSubscription = DeviceEventEmitter.addListener(
    CLASS_READ_MESSAGE,
    (data: ReadMessageData): void => {
      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveReadMessageMsg', ...data })
      );
      const { messageId } = data;
      let { messageList, bubbleMessageList } = state;
      messageList = messageList.map(
        (message): Message => ({
          ...message,
          isRead: message.messageId === messageId ? true : message.isRead
        })
      );
      bubbleMessageList = bubbleMessageList.map(
        (message): Message => ({
          ...message,
          isRead: message.messageId === messageId ? true : message.isRead
        })
      );
      setMessageList(messageList);
      setBubbleMessageList(bubbleMessageList);
    }
  );
};

export const stopReceiveReadMessageMsg = (): void => {
  if (readMessageSubscription) {
    readMessageSubscription.remove();
  }
};

export const startReceiveWithDrawMessageMsg = (): void => {
  withDrawMessageSubscription = DeviceEventEmitter.addListener(
    CLASS_WITH_DRAW_MESSAGE,
    (data: WithDrawMessageData): void => {
      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveWithDrawMessageMsg', ...data })
      );
      const { messageId } = data;
      withDrawMessageWithMessageId(messageId);
    }
  );
};

export const stopReceiveWithDrawMessageMsg = (): void => {
  if (withDrawMessageSubscription) {
    withDrawMessageSubscription.remove();
  }
};

export const startReceiveCurrentTimeChangeMsg = (): void => {
  currentTimeChangeSubscription = DeviceEventEmitter.addListener(
    CLASS_ROOM_EVENT_CURRENT_TIME_CHANGE,
    (data: CurrentTimeChangeData): void => {
      const { lessonStartTime, isVod, vodMessageList, messageList } = state;
      if (!isVod) return;

      const { time } = data;
      const currentDate = moment(lessonStartTime)
        .add(Math.floor(time / 1000), 'seconds')
        .format('YYYY-MM-DD HH:mm:ss');
      const resMessageList = vodMessageList.filter((message): boolean =>
        moment(message.sendTime).isBefore(currentDate)
      );

      if (messageList.length === resMessageList.length) {
        return;
      }
      setMessageList(resMessageList);
    }
  );
};

export const stopReceiveCurrentTimeChangeMsg = (): void => {
  if (currentTimeChangeSubscription) {
    currentTimeChangeSubscription.remove();
  }
};

export const initChatRoom = (): void => {
  startReceiveNewMessageMsg();
  startReceiveReadMessageMsg();
  startReceiveWithDrawMessageMsg();
};

export const destroyChatRoom = (): void => {
  stopReceiveNewMessageMsg();
  stopReceiveReadMessageMsg();
  stopReceiveWithDrawMessageMsg();
};
